﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Gargoyle.Roton
{
    /// <summary>
    /// A collection of files. This can represent a collection either in memory or on disk.
    /// </summary>
    public class Cache
    {
        private class Entry
        {
            public string FileName;
            public byte[] Data;

            public Entry(string newFileName, byte[] newData)
            {
                FileName = newFileName;
                Data = newData;
            }
        }

        /// <summary>
        /// If on disk, this is the path where files can be found.
        /// </summary>
        public string Path;

        private List<Entry> Entries;

        /// <summary>
        /// Create a new cache with an optional path.
        /// </summary>
        /// <param name="newPath"></param>
        public Cache(string newPath)
        {
            Path = CleanPath(newPath);
            Entries = new List<Entry>();
        }

        private string CleanFileName(string inputFileName)
        {
            int index = 0;
            char[] invalidCharacters = System.IO.Path.GetInvalidFileNameChars();
            while (true)
            {
                index = inputFileName.IndexOfAny(invalidCharacters, index);
                if (index < 0)
                    break;
                inputFileName = inputFileName.Substring(0, index) + "_" + inputFileName.Substring(index + 1);
            }
            return inputFileName;
        }

        private string CleanPath(string inputPath)
        {
            if (inputPath.IndexOfAny(System.IO.Path.GetInvalidPathChars(), 0) >= 0)
            {
                return "";
            }
            return inputPath;
        }

        private Entry FindCacheEntry(string fileName)
        {
            fileName = fileName.ToUpperInvariant();

            foreach (Entry e in Entries)
            {
                if (e.FileName.ToUpperInvariant() == fileName)
                    return e;
            }

            return null;
        }

        private Entry FindDiskEntry(string fileName)
        {
            if (Path != "") {
                string fullFileName = System.IO.Path.Combine(Path, fileName);

                if (File.Exists(fullFileName))
                {
                    return new Entry(fileName, File.ReadAllBytes(fullFileName));
                }                
            }

            return null;
        }

        private Entry FindEntry(string fileName)
        {
            Entry result;
            
            result = FindCacheEntry(fileName);
            if (result == null)
                result = FindDiskEntry(fileName);

            return result;
        }

        /// <summary>
        /// Get a list of all filenames that have a specific extension.
        /// </summary>
        /// <param name="extension">Extension to find.</param>
        /// <returns>An array of all filenames that match the extension.</returns>
        public string[] FindFiles(string extension)
        {
            List<string> result = new List<string>();
            string[] localFiles;

            for (int j = 0; j < Entries.Count; j++)
            {
                if (Entries[j].FileName.ToUpperInvariant().EndsWith(extension.ToUpperInvariant()))
                {
                    result.Add(System.IO.Path.GetFileNameWithoutExtension(Entries[j].FileName.ToUpperInvariant()));
                }
            }

            if (Path != "")
            {
                localFiles = Directory.GetFiles(Path, "*." + extension);
                if (localFiles.Length > 0)
                {
                    for (int j = 0; j < localFiles.Length; j++)
                    {
                        string fileName = System.IO.Path.GetFileNameWithoutExtension(localFiles[j]).ToUpperInvariant();
                        if (!result.Contains(fileName))
                            result.Add(fileName);
                    }
                }
            }

            result.Sort();
            result.Add("Cancel");
            return result.ToArray();
        }

        /// <summary>
        /// Import all files from another cache.
        /// </summary>
        /// <param name="source">Cache to import from.</param>
        public void Import(Cache source)
        {
            foreach (Entry entry in source.Entries)
            {
                SaveFile(entry.FileName, entry.Data);
            }
        }

        /// <summary>
        /// Load a file from the cache.
        /// </summary>
        /// <param name="fileName">Filename of the desired file.</param>
        /// <returns>Data from the file. If the file cannot be found, returns null.</returns>
        public byte[] LoadFile(string fileName)
        {
            byte[] result = null;
            fileName = CleanFileName(fileName);

            if (fileName != "")
            {
                Entry entry = FindEntry(fileName);
                if (entry != null)
                    result = entry.Data;
            }

            return result;
        }

        /// <summary>
        /// Save a file to the cache.
        /// </summary>
        /// <param name="fileName">Filename of the desired file.</param>
        /// <param name="data">Data to write to the cache.</param>
        public void SaveFile(string fileName, byte[] data)
        {
            fileName = CleanFileName(fileName);

            if (fileName != "")
            {
                byte[] newData = new byte[data.Length];
                Array.Copy(data, newData, data.Length);

                Entry entry = FindCacheEntry(fileName);
                if (entry == null)
                {
                    if (Path != "")
                        File.WriteAllBytes(System.IO.Path.Combine(Path, fileName), newData);
                    else
                    {
                        entry = new Entry(fileName, newData);
                        Entries.Add(entry);
                    }
                }
                else
                    entry.Data = newData;
            }
        }
    }
}
